Skip to content

Fix phpstan/phpstan#13380: Promoting protected property to public in subclass generates uninitialized property#5353

Open
phpstan-bot wants to merge 3 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-55kt220
Open

Fix phpstan/phpstan#13380: Promoting protected property to public in subclass generates uninitialized property#5353
phpstan-bot wants to merge 3 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-55kt220

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a child class redeclares a property from a parent class to change its visibility (e.g. protected to public), and the parent class initializes that property via constructor promotion, PHPStan incorrectly reported the property as uninitialized. This fix recognizes that inherited constructors with promoted properties properly initialize the redeclared property.

Changes

  • Modified src/Node/ClassPropertiesNode.php: Added a check in getUninitializedProperties() that detects when a property is promoted in the inherited constructor's declaring class
  • Added tests/PHPStan/Rules/Properties/data/bug-13380.php: Regression test data with two scenarios
  • Added testBug13380() in tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php

Root cause

In ClassPropertiesNode::getUninitializedProperties(), the initialization check only looked at whether the property was promoted in the current class ($property->isPromoted()). When a child class redeclares the property (to widen visibility), the property node in the child class is not promoted — it's a plain property declaration. The parent's constructor body isn't in the child's returnStatementNodes, so collectUninitializedProperties() also couldn't detect the initialization.

The fix adds a check: if the class has a constructor inherited from a parent class, and that parent class promotes the property, then the property is considered initialized.

Test

The regression test covers:

  • Bar extends Foo — redeclares $prop as public (no own constructor) → should have no error
  • Baz extends Foo — redeclares $prop as public with own empty constructor → should report uninitialized

Fixes phpstan/phpstan#13380

… false uninitialized error

- When a child class redeclares a parent's promoted property (e.g. to widen visibility), the property was incorrectly reported as uninitialized
- Added check in ClassPropertiesNode::getUninitializedProperties() to detect when the inherited constructor's declaring class promotes the redeclared property
- Added regression test with both the valid case (inherited constructor) and invalid case (own constructor without parent::__construct)
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A case like

class Foo
{
	public function __construct(
		protected string $prop,
	){
	}
}

class Bar extends Foo
{
	public function __construct(
		string $prop,
	){
		parent::__construct($prop);
	}
}

class Baz extends Bar {
	public string $prop;
}

should be supported too.

When an intermediate class declares its own constructor that calls
parent::__construct(), the promoted property lives in a grandparent
class. Walk up the ancestor chain from the constructor's declaring
class instead of only checking that single class.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Background test suite also completed successfully. All changes have been committed and pushed.

Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think

class Foo3
{
	public function __construct(
		protected string $prop,
	){
	}
}

class Bar3 extends Foo3
{
	public function __construct(
	){
	}
}

class Baz3 extends Bar3 {
	public string $prop;
}

should still be reported then

…ote it

When an intermediate class overrides the constructor without a matching
parameter for the promoted property, the ancestor walk now stops instead
of incorrectly assuming the property is initialized. Uses constructor
parameter matching as a heuristic to determine if parent::__construct
is likely called.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

The background test suite also completed successfully (all 11708 tests passed). The fix has been committed and pushed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants